--[[
	Gathers statistics for the auctions found.
	
	Copyright (C) Udorn (Blackhand)
	
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.	
--]]

vendor.Statistic = vendor.Vendor:NewModule("Statistic");
local L = vendor.Locale.GetInstance()
local self = vendor.Statistic;
local log = vendor.Debug:new("Statistic")

--[[
	Applies the current settings.
--]]
local function _ApplySettings(self)
	if (self.db.profile.statisticsBeta) then
		local gatherer = vendor.Vendor:GetModule("Gatherer")
		if (gatherer and not gatherer:IsEnabled()) then
			log:Debug("enable Gatherer")
			gatherer:Enable()
		end
	else
		local gatherer = vendor.Vendor:GetModule("Gatherer")
		if (gatherer and gatherer:IsEnabled()) then
			log:Debug("disable Gatherer")
			gatherer:Disable()
		end
	end
end

--[[
	Sets the given value in the profile.
--]]
local function _SetValue(info, value)
	self.db.profile[info.arg] = value;
	_ApplySettings(self)
end

--[[
	Returns the given value from the profile.
--]]
local function _GetValue(info)
	return self.db.profile[info.arg]
end

--[[
	Update the statistic for given item.
--]]
local function _UpdateStatistic(self, itemLink, minBids, buyouts, isNeutral)
	-- calculate the minbid average
	table.sort(minBids);
	table.sort(buyouts);
	vendor.Math.CleanupByStandardDeviation(minBids, self.db.profile.smallerStdDevMul, self.db.profile.largerStdDevMul)
	vendor.Math.CleanupByStandardDeviation(buyouts, self.db.profile.smallerStdDevMul, self.db.profile.largerStdDevMul)
	local avgMinBid = vendor.Math.GetMedian(minBids);
	local avgBuyout = vendor.Math.GetMedian(buyouts);
	local itemInfo = self.itemInfo
	if (not vendor.Items:GetItemInfo(itemLink, itemInfo, isNeutral)) then
		vendor.Items:InitItemInfo(itemInfo)
	end
	if (avgMinBid > 0) then
		-- update moving average of the medians
		if (itemInfo.avgMinBid > 0) then
			itemInfo.avgMinBid = vendor.Math.UpdateAverage(itemInfo.avgMinBid, self.db.profile.movingAverage, avgMinBid, 1);
		else
			itemInfo.avgMinBid = avgMinBid;
		end
	end
	if (avgBuyout > 0) then
		-- update moving average of the medians
		if (itemInfo.avgBuyout > 0) then
			itemInfo.avgBuyout = vendor.Math.UpdateAverage(itemInfo.avgBuyout, self.db.profile.movingAverage, avgBuyout, 1);
		else
			itemInfo.avgBuyout = avgBuyout;
		end
	end
	-- write back item
	vendor.Items:PutItemInfo(itemLink, isNeutral, itemInfo);
end

--[[
	Returns avgMinBid, avgBuyout, minMinBid and minBuyout for the given scan result.
--]]
local function _GetAvgs(result, deactivateStdDev)
	local minBids = {};
	local buyouts = {};
	local sum=0;
	local mySum=0;
	for i = 1, result:Size() do
		local itemLinkKey, time, timeLeft, count, minBid, minIncrement, buyout, _, owner = result:Get(i);
		if (itemLinkKey) then
			if (minBid and minBid > 0) then
				table.insert(minBids, math.floor(minBid / count + 0.5));
			end
			if (buyout and buyout > 0) then
				table.insert(buyouts, math.floor(buyout / count + 0.5));
			end
			sum = sum + count;
			
			if (owner) then
				for k,_ in pairs(VendorDb.profileKeys) do
					local sMyRoleName,_=strsplit(" ",k);
					if sMyRoleName == owner then
						mySum = mySum + count;
						break;
					end
				end
			end
		end
	end
	table.sort(minBids);
	table.sort(buyouts);
	if (not deactivateStdDev) then
		vendor.Math.CleanupByStandardDeviation(minBids, self.db.profile.smallerStdDevMul, self.db.profile.largerStdDevMul)
		vendor.Math.CleanupByStandardDeviation(buyouts, self.db.profile.smallerStdDevMul, self.db.profile.largerStdDevMul)
	end
	local avgMinBid = vendor.Math.GetMedian(minBids);
	local avgBuyout = vendor.Math.GetMedian(buyouts);
	local minMinBid = nil
	local minBuyout = nil
	if (table.getn(minBids) > 0) then
	 	minMinBid = minBids[1]
	end
	if (table.getn(buyouts) > 0) then
		minBuyout = buyouts[1]
	end
	return avgMinBid, avgBuyout, minMinBid, minBuyout, sum, mySum;
end

function GetScanSetAvgs(result)
	return _GetAvgs(result);
end
	
--[[
	Appends the given money description to the tooltip
--]]
local function _AddMoney(self, tooltip, prompt, bid, buyout)
	local msg = vendor.Format.FormatMoney(bid, true).."/"..vendor.Format.FormatMoney(buyout, true)
	tooltip:AddDoubleLine(prompt, msg)
end

--[[
	Adds auction information to the GameTooltip of the currently available snapshot.
--]]
local function _AddCurrentAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral, bInverse)
	if (self.db.profile.showCurrent) then
		local result = vendor.Scanner:GetScanSet(isNeutral, itemLink, bInverse);
		--MyTricksInfo("_AddCurrentAuctionInfo result:Size()="..result:Size() );
		if (result and result:Size() > 0) then
			local avgMinBid, avgBuyout,_,_,sum,mySum = _GetAvgs(result);
			if mySum <= 0 then
				tooltip:AddLine(L["Current auctions (%d)"]:format(sum));
			else
				tooltip:AddLine(L["Current auctions (%d/%d)"]:format(mySum,sum));
			end
			local msg1, msg2;
			-- avg minbid
			if (avgMinBid > 0) then
				if (count > 1) then
					msg1 = L["Bid (%d)"]:format(count);
					msg2 = vendor.Format.FormatMoney(avgMinBid, true).." ("..vendor.Format.FormatMoney(avgMinBid * count, true)..")";
				else
					msg1 = L["Bid"];
					msg2 = vendor.Format.FormatMoney(avgMinBid, true);
				end
				tooltip:AddDoubleLine(msg1, msg2);
			end
			if (avgBuyout > 0) then
				-- avg buyout
				if (count > 1) then
					msg1 = L["Buyout (%d)"]:format(count);
					msg2 = vendor.Format.FormatMoney(avgBuyout, true).." ("..vendor.Format.FormatMoney(avgBuyout * count, true)..")";
				else
					msg1 = L["Buyout"];
					msg2 = vendor.Format.FormatMoney(avgBuyout, true);
				end
				tooltip:AddDoubleLine(msg1, msg2);
			end
		end
	end
end

--[[
	Adds auction information to the GameTooltip.
--]]
local function _AddAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral)
	if (self.db.profile.showHistory) then
		local msg1, msg2;
		-- avg minbid
		if (itemInfo.avgMinBid > 0) then
			if (count > 1) then
				msg1 = L["Bid (%d)"]:format(count);
				msg2 = vendor.Format.FormatMoney(itemInfo.avgMinBid).." ("..vendor.Format.FormatMoney(itemInfo.avgMinBid * count)..")";
			else
				msg1 = L["Bid"];
				msg2 = vendor.Format.FormatMoney(itemInfo.avgMinBid);
			end
			tooltip:AddDoubleLine(msg1, msg2);
		end
		if (itemInfo.avgBuyout > 0) then
			-- avg buyout
			if (count > 1) then
				msg1 = L["Buyout (%d)"]:format(count);
				msg2 = vendor.Format.FormatMoney(itemInfo.avgBuyout).." ("..vendor.Format.FormatMoney(itemInfo.avgBuyout * count)..")";
			else
				msg1 = L["Buyout"];
				msg2 = vendor.Format.FormatMoney(itemInfo.avgBuyout);
			end
			tooltip:AddDoubleLine(msg1, msg2);
		end
	end
end

--[[
	Adds the beta statistics.
--]]
local function _AddBetaAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral)
	local avgBid, avgBuyout, numAuctions, numBuyouts = vendor.Gatherer:GetAuctionInfo(itemLink, isNeutral)
	if (avgBid) then
		tooltip:AddLine(string.format("(Beta) All-time auctions (%d auctions, %d buyouts)", numAuctions or 0, numBuyouts or 0))
		_AddMoney(self, tooltip, "(Beta) Avg", avgBid, avgBuyout or 0)
	end
	local avgBid, avgBuyout, lowerBid, lowerBuyout, upperBid, upperBuyout, 
		numAuctions, numBuyouts = vendor.Gatherer:GetCurrentAuctionInfo(itemLink)
	if (avgBid) then
		tooltip:AddLine(string.format("(Beta) Current auctions (%d auctions, %d buyouts)", numAuctions, numBuyouts))
		_AddMoney(self, tooltip, "(Beta) Avg", avgBid, avgBuyout or 0)
		_AddMoney(self, tooltip, "(Beta) Lower", lowerBid, lowerBuyout or 0)
		_AddMoney(self, tooltip, "(Beta) Upper", upperBid, upperBuyout or 0)
	end
end

--[[
	Options for this module.
--]]
local function _CreateOptions()
	vendor.Vendor.options2.args.statistic = {
		type = "group",
		name = L["Statistics"],
		desc = L["Statistics"],
		args = {
			showHistory = {
				type = "toggle",
				name = L["Show historically averages"],
				desc = L["Selects whether historically average values from auction scans should be shown in the GameTooltip."],
				get = _GetValue,
				set = _SetValue,
				arg = "showHistory",
				order = 1,
			},
			showCurrent = {
				type = "toggle",
				name = L["Show current averages"],
				desc = L["Selects whether current average values from auction scans should be shown in the GameTooltip."],
				get = _GetValue,
				set = _SetValue,
				arg = "showCurrent",
				order = 2,
			},
			movAvg = {
				type = "range",
				name = L["Moving average"],
				desc = L["Selects the number of (approximated) values, that should be taken for the moving average of the historically auction scan statistics."],
				min = 2,
				max = 256,
				step = 1,
				get = _GetValue,
				set = _SetValue,
				arg = "movingAverage",
				order = 3,
			},
			smallerStdDevMul = {
				type = "range",
				name = L["< standard deviation multiplicator"],
				desc = L["Selects the standard deviation multiplicator for statistical values to be removed, which are smaller than the average. The larger the multiplicator is selected, the lesser values are removed from the average calculation."],
				min = 1,
				max = 10,
				step = 0.1,
				get = _GetValue,
				set = _SetValue,
				arg = "smallerStdDevMul",
				order = 4,
			},
			largerStdDevMul = {
				type = "range",
				name = L["> standard deviation multiplicator"],
				desc = L["Selects the standard deviation multiplicator for statistical values to be removed, which are larger than the average. The larger the multiplicator is selected, the lesser values are removed from the average calculation."],
				min = 1,
				max = 10,
				step = 0.1,
				get = _GetValue,
				set = _SetValue,
				arg = "largerStdDevMul",
				order = 5,
			},			
			statisticsBeta = {
				type = "toggle",
				name = "New statistics module (BETA)", -- I18N
				desc = "Enables the new statistics module. It's currently declared as beta.", -- I18N
				get = _GetValue,
				set = _SetValue,
				arg = "statisticsBeta",
				order = 6,
			},			
		}
	}
	vendor.Vendor.AceConfigRegistry:RegisterOptionsTable("AuctionMaster Statistic", vendor.Vendor.options2.args.statistic)
	vendor.Vendor.AceConfigDialog:AddToBlizOptions("AuctionMaster Statistic", L["Statistics"], "AuctionMaster")
end

--[[
	Initializes the module.
--]]
function vendor.Statistic:OnInitialize()
	self.db = vendor.Vendor.db:RegisterNamespace("Statistic", {
		profile = {
    		showHistory = true,
    		showCurrent = true,
    		movingAverage = 12,
    		smallerStdDevMul = 2,
    		largerStdDevMul = 2,
    	}
	});
	self.itemInfo = {} -- caching the table
	_CreateOptions();	
end

--[[
	Initializes the module.
--]]
function vendor.Statistic:OnEnable()
	vendor.TooltipHook:AddAppender(self, 20);
	vendor.Scanner:AddScanSnapshotListener(self, 1);
	_ApplySettings(self)
end

--[[
	ScanResultListener interface method for new or updated
	Scansnapshots.
	The complete new scan will be considered, not only the difference, because we are
	working with the median.
--]]
function vendor.Statistic:ScanSnapshotUpdated(snapshot, isNeutral)
	vendor.Vendor:Debug("scan update enter");
	local currentItem = nil; -- current entry for gathering stats
	local minBids = {}; -- minbids collected for current item
	local buyouts = {}; -- buyouts collected for current item
   	for i = 1, snapshot:Size() do
      	local itemLinkKey, time, timeLeft, count, minBid, minIncrement, buyoutPrice = snapshot:Get(i);
      	if (itemLinkKey) then
      		if (currentItem and currentItem ~= itemLinkKey) then
      			-- a new item, handle the old one
				_UpdateStatistic(self, currentItem, minBids, buyouts, isNeutral);
				minBids = {}; -- need table:clear(), or sort(table, n)
				buyouts = {};
			end
			currentItem = itemLinkKey;
			if (minBid > 0) then
				table.insert(minBids, math.floor(minBid / count + 0.5));
      		end
			if (buyoutPrice > 0) then
				table.insert(buyouts, math.floor(buyoutPrice / count + 0.5));
   			end
      	end
   	end
	if (currentItem) then
		_UpdateStatistic(self, currentItem, minBids, buyouts, isNeutral);
	end
	vendor.Vendor:Debug("Statistic:ScanSnapshotUpdated leave");
end

--[[
	Callback for GameTooltip integration.
--]]
function vendor.Statistic:AppendToGameTooltip(tooltip, itemLink, itemName, count, bInverse)
	local itemLinkKey = vendor.Items:GetItemLinkKey(itemLink);
	--local isNeutral = vendor.AuctionHouse:IsNeutral();
	local isNeutral = false;
	local itemInfo = self.itemInfo
	if (vendor.Items:GetItemInfo(itemLinkKey, itemInfo, isNeutral,bInverse)) then
		_AddAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral);
   	end
  
	_AddCurrentAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral, bInverse);
	
	if (self.db.profile.statisticsBeta) and ( not bInverse ) then
		_AddBetaAuctionInfo(self, tooltip, itemInfo, itemLink, count, isNeutral);
	end
end

--[[
	Returns avgMinBid, avgBuyout, minMinBid and minBuyout for the current 
	auctions for the specified item or nil. The results may be cleaned up
	by a standard deviation.
--]]
function vendor.Statistic:GetCurrentAuctionInfo(itemLink, isNeutral, deactivateStdDev)
	local result = vendor.Scanner:GetScanSet(isNeutral, itemLink);
	if (result and result:Size() > 0) then
		return _GetAvgs(result, deactivateStdDev);
	end
	return nil;
end

--[[
	Returns the avgMinBid and avgBuyout for the given item.
--]]
function vendor.Statistic:GetAuctionInfo(itemLink, isNeutral)
	local itemLinkKey = vendor.Items:GetItemLinkKey(itemLink);
	if (vendor.Items:GetItemInfo(itemLinkKey, self.itemInfo, isNeutral)) then
		return self.itemInfo.avgMinBid, self.itemInfo.avgBuyout
	end
	return nil;
end
